// Fill out your copyright notice in the Description page of Project Settings.


#include "NPCBaseCpp.h"
#include "NPCAreaBaseCpp.h"
#include "PlayerBaseCpp.h"
#include "GameModeBaseCpp.h"

ANPCBaseCpp::ANPCBaseCpp()
{
	PrimaryActorTick.bCanEverTick = true;
	BoxCollider = CreateDefaultSubobject<UBoxComponent>(TEXT("Box Collider"));
	RootComponent = BoxCollider;

	SpriteFlipBook = CreateDefaultSubobject<UPaperFlipbookComponent>(TEXT("FlipBook"));
	SpriteFlipBook->SetSimulatePhysics(false);
	SpriteFlipBook->SetupAttachment(RootComponent);

	MoveDirection = GetActorRightVector();
	
	ActionState = ENPCAction::E_Walk;
	AnimState = ENPCAnim::E_IdleLeft;
	Tags.Add(FName("NPC"));
	locked = true;
}

bool ANPCBaseCpp::Run(const float DeltaTime, const FVector TargetLocation)
{
	const FVector CurrentLocation = GetActorLocation();
	const FVector NPCToTargetLocation = TargetLocation - CurrentLocation;
	const float Dot = FVector::DotProduct(NPCToTargetLocation, MoveDirection);
	if (Dot == 0)
	{
		return false;
	}
	if (Dot < 0)
	{
		MoveDirection = FVector::ZeroVector - MoveDirection;
		if (AnimState == ENPCAnim::E_RunLeft)
		{
			UpdateAnimStateMachine(ENPCAnim::E_RunRight);
		}
		else
		{
			UpdateAnimStateMachine(ENPCAnim::E_RunLeft);
		}
	}
	FHitResult Result;
	RayTrace(CurrentLocation, CurrentLocation + MoveDirection * SafeDistance, Result);
	if (Result.GetActor() != nullptr)
	{
		if (Result.GetActor()->ActorHasTag(FName("Player")))
		{
			return true;
		}
		else if (Result.GetActor()->ActorHasTag(FName("NPC")))
		{
			return false;
		}
	}
	const FVector NewLocation = CurrentLocation + DeltaTime * RunSpeed * MoveDirection;
	if (NewLocation.X > LeftSide.X && NewLocation.X < RightSide.X)
	{
		SetActorLocation(CurrentLocation + DeltaTime * RunSpeed * MoveDirection);
	}
	return false;
}

void ANPCBaseCpp::Walk(const float DeltaTime)
{
	const FVector CurrentLocation = GetActorLocation();
	const FVector NewLocation = CurrentLocation + DeltaTime * WalkSpeed * MoveDirection;
	if (NewLocation.X < LeftSide.X || NewLocation.X > RightSide.X)
	{
		MoveDirection = FVector::ZeroVector - MoveDirection;
		if (MoveDirection.X > 0.0f)
		{
			UpdateAnimStateMachine(ENPCAnim::E_WalkRight);
		}
		else if (MoveDirection.X < 0.0f)
		{
			UpdateAnimStateMachine(ENPCAnim::E_WalkLeft);
		}
	}
	else
	{
		SetActorLocation(CurrentLocation + DeltaTime * WalkSpeed * MoveDirection);
	}
}

void ANPCBaseCpp::Attack(const float AttackAnimTime, const float DamageTriggerTime)
{
	if (AbilityBusy)
	{
		return;
	}
	const FTimerDelegate AttackTimerDelegate = FTimerDelegate::CreateUObject(this, &ANPCBaseCpp::AttackTimerCallback);
	GetWorldTimerManager().SetTimer(AttackTimerHandle, AttackTimerDelegate, AttackAnimTime, false);
	const FTimerDelegate DamageTimerDelegate = FTimerDelegate::CreateUObject(this, &ANPCBaseCpp::DamageTimerCallback);
	GetWorldTimerManager().SetTimer(DamageTimerHandle, DamageTimerDelegate, DamageTriggerTime, false);
	const ENPCAnim TargetAnimState = GetAnimStateByAction(ActionState, ENPCAction::E_Attack);
	UpdateAnimStateMachine(TargetAnimState);
	AbilityBusy = true;
}

void ANPCBaseCpp::AttackTimerCallback()
{
	GetWorldTimerManager().ClearTimer(AttackTimerHandle);
	AbilityBusy = false;
	if (ParentArea->GetPlayerInArea() == nullptr)
	{
		const ENPCAnim TargetAnimState = GetAnimStateByAction(ActionState, ENPCAction::E_Walk);
		UpdateAnimStateMachine(TargetAnimState);
	}
	else
	{
		const ENPCAnim TargetAnimState = GetAnimStateByAction(ActionState, ENPCAction::E_Run);
		UpdateAnimStateMachine(TargetAnimState);
	}
}

void ANPCBaseCpp::DamageTimerCallback()
{
	GetWorldTimerManager().ClearTimer(DamageTimerHandle);
	AActor* PlayerActor = ParentArea->GetPlayerInArea();
	if (PlayerActor == nullptr)
	{
		return;
	}
	APlayerBaseCpp* Player = Cast<APlayerBaseCpp>(PlayerActor);
	if (Player == nullptr)
	{
		return;
	}
	UGameplayStatics::ApplyDamage(Player, BaseDamage, nullptr, this, nullptr);
}

FVector ANPCBaseCpp::GetLeftSide() const
{
	return LeftSide;
}

FVector ANPCBaseCpp::GetRightSide() const
{
	return RightSide;
}

ENPCAnim ANPCBaseCpp::GetAnimStateByAction(ENPCAction& CurrentAction, ENPCAction TargetAction)
{
	if (CurrentAction == TargetAction)
	{
		return AnimState;
	}
	ENPCAnim TargetAnimState = ENPCAnim::E_IdleLeft;
	CurrentAction = TargetAction;
	switch (TargetAction)
	{
		case ENPCAction::E_Idle:
			{
				if (AnimState == ENPCAnim::E_AttackLeft)
				{
					TargetAnimState = ENPCAnim::E_IdleLeft;
				}
				else if (AnimState == ENPCAnim::E_AttackRight)
				{
					TargetAnimState = ENPCAnim::E_AttackRight;
				}
				break;
			}
		case ENPCAction::E_Walk:
			{
				if (AnimState == ENPCAnim::E_IdleLeft || AnimState == ENPCAnim::E_RunLeft)
				{
					TargetAnimState = ENPCAnim::E_WalkLeft;
				}
				else if (AnimState == ENPCAnim::E_IdleRight || AnimState == ENPCAnim::E_RunRight)
				{
					TargetAnimState = ENPCAnim::E_WalkRight;
				}
				break;
			}
		case ENPCAction::E_Run:
			{
				if (AnimState == ENPCAnim::E_WalkLeft || AnimState == ENPCAnim::E_IdleLeft || AnimState == ENPCAnim::E_AttackLeft)
				{
					TargetAnimState = ENPCAnim::E_RunLeft;
				}
				else if (AnimState == ENPCAnim::E_WalkRight || AnimState == ENPCAnim::E_IdleRight || AnimState == ENPCAnim::E_AttackRight)
				{
					TargetAnimState = ENPCAnim::E_RunRight;
				}
				break;
			}
		case ENPCAction::E_Attack:
			{
				if (AnimState == ENPCAnim::E_IdleLeft || AnimState == ENPCAnim::E_RunLeft)
				{
					TargetAnimState = ENPCAnim::E_AttackLeft;
				}
				else if (AnimState == ENPCAnim::E_IdleRight || AnimState == ENPCAnim::E_RunRight)
				{
					TargetAnimState = ENPCAnim::E_AttackRight;
				}
				break;
			}
		case ENPCAction::E_Dead:
			{
				if (AnimState == ENPCAnim::E_IdleLeft || AnimState == ENPCAnim::E_WalkLeft || AnimState == ENPCAnim::E_RunLeft || AnimState == ENPCAnim::E_AttackLeft)
				{
					TargetAnimState = ENPCAnim::E_DeadLeft;
				}
				else if (AnimState == ENPCAnim::E_IdleRight || AnimState == ENPCAnim::E_WalkRight || AnimState == ENPCAnim::E_RunRight || AnimState == ENPCAnim::E_AttackRight)
				{
					TargetAnimState = ENPCAnim::E_DeadRight;
				}
				break;
			}
		case ENPCAction::E_Attack1:
			{
				TargetAnimState = ENPCAnim::E_Attack1;
				break;
			}
		case ENPCAction::E_Attack2:
			{
				TargetAnimState = ENPCAnim::E_Attack2;
				break;
			}
		case ENPCAction::E_Attack3:
			{
				TargetAnimState = ENPCAnim::E_Attack3;
				break;
			}
		default:
			break;
	}
	return TargetAnimState;
}

void ANPCBaseCpp::NotifyNPCOnChangingActionState(const ENPCAction TargetAction)
{
	const ENPCAnim TargetAnimState = GetAnimStateByAction(ActionState, TargetAction);
	UpdateAnimStateMachine(TargetAnimState);
	if (TargetAction == ENPCAction::E_Walk)
	{
		WalkSpeed = FMath::RandRange(RunSpeed * 0.25f, RunSpeed * 0.75f);
	}
}

void ANPCBaseCpp::SetDead()
{
	ParentArea->UpdateNPCsState(this);
	const ENPCAnim TargetAnimState = GetAnimStateByAction(ActionState, ENPCAction::E_Dead);
	UpdateAnimStateMachine(TargetAnimState);
	AGameModeBaseCpp* CurrentGameMode = Cast<AGameModeBaseCpp>(UGameplayStatics::GetGameMode(GetWorld()));
	if (CurrentGameMode == nullptr)
	{
		return;
	}
	const FVector Location = GetActorLocation();
	if (DropItemAmount > 0)
	{
		CurrentGameMode->SpawnDroppedItem(DropItemType, Location);
	}
}

float ANPCBaseCpp::TakeDamage(float DamageAmount, FDamageEvent const& DamageEvent, AController* EventInstigator, AActor* DamageCauser)
{
	DecreaseCurrentHP(DamageAmount);
	return 0.0f;
}

void ANPCBaseCpp::DeadTimerCallback()
{
	
}

void ANPCBaseCpp::SetSpawnInfo(const FString Id, const float HPConfig, const FVector LeftSideConfig, const FVector RightSideConfig, const FVector SpawnLocationConfig, const FVector SpawnDirectionConfig, ANPCAreaBaseCpp* Parent, const int DropAmountConfig, const EItemType DropTypeConfig)
{
	NPCId = Id;
	SetInitHP(HPConfig);
	LeftSide = LeftSideConfig;
	RightSide = RightSideConfig;
	SpawnLocation = SpawnLocationConfig;
	SpawnDirection = SpawnDirectionConfig;

	MoveDirection = SpawnDirection;
	ParentArea = Parent;
	if (MoveDirection.X > 0)
	{
		UpdateAnimStateMachine(ENPCAnim::E_IdleRight);
	}
	else
	{
		UpdateAnimStateMachine(ENPCAnim::E_IdleLeft);
	}

	if (ParentArea != nullptr)
	{
		ParentArea = Parent;
		ParentArea->RegisterToNPCsState(this);
	}
	DropItemAmount = DropAmountConfig;
	DropItemType = DropTypeConfig;
	locked = false;
}

FString ANPCBaseCpp::GetNPCId() const
{
	return NPCId;
}

